﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.ComponentModel;
using System.IO;
using System.Windows.Forms;
using System.Globalization;

namespace BalancaDummie
{
    // Delegates que possibilitam a assinatura dos eventos
    public delegate void ConnectEventHandler(object sender, BalancaEventArgs e);
    public delegate void DisconnectEventHandler(object sender, BalancaEventArgs e);
    public delegate void SendEventHandler(object sender, BalancaEventArgs e);
    public delegate void RecieveEventHandler(object sender, BalancaEventArgs e);
    public delegate void StatusChangeEventHandler(object sender, BalancaEventArgs e);

    class Balanca
    {
        private Object thislock = new Object();
        private Random random = new Random();

        private IPEndPoint endPoint;

        private volatile bool escutar;
        private volatile bool comunicar;
        private volatile byte[] buffer;
        private volatile String mensagem;


        private Thread ThreadEscutar { get; set; }
        private Thread ThreadComunicar { get; set; }
        private Thread ThreadEmbarque { get; set; }

        private TcpListener Listener { get; set; }

        private int taraVar;
        private int pesoAtual;
        private string subtotalizadorAtual;
        private int velocidadeAtual = 0;
        private int[] poroes;

        public bool IsListening { get { return escutar; } }
        public bool IsConnected { get { return comunicar; } }

        public int Porta { get; private set; }        

        public Embarque Embarque { get; set; }
        public int Tara { get; set; }

        /// <summary>
        /// Construtor
        /// </summary>
        /// <param name="porta">Porta TCP</param>
        public Balanca(int porta)
        {
            this.Porta = porta;
            endPoint = new IPEndPoint(IPAddress.Any, porta);

            buffer = new byte[4096];
            mensagem = String.Empty;

            poroes = new int[15];
        }

        /// <summary>
        /// Inicia a operação da balança
        /// </summary>
        public void Iniciar()
        {
            ThreadEscutar = new Thread(new ThreadStart(Escutar));
            ThreadEscutar.Name = "Escutador";

            if (Embarque != null)
                Embarque.Embarcando = true;

            try
            {
                escutar = true;
                ThreadEscutar.Start();
            }
            catch (Exception e)
            {
                MudarEstado("ERRO AO INICIAR ESCUTADOR! " + e.Message);
            }
        }

        /// <summary>
        /// Encerra a operação da balança
        /// </summary>
        public void Parar()
        {
            escutar = false;
            comunicar = false;

            if (Embarque != null)
                Embarque.Embarcando = false;

            MudarEstado("Servidor Parado");
        }

        public void IniciarEmbarque()
        {
            ThreadEmbarque = new Thread(new ThreadStart(Embarcar));
            ThreadEmbarque.Name = "Embarcador";

            try
            {
                ThreadEmbarque.Start();
                Embarque.Embarcando = true;
            }
            catch (Exception e)
            {
                MudarEstado("ERRO AO INICIAR EMBARQUE! " + e.Message);
            }
        }

        public void PararEmbarque()
        {
            if (Embarque != null)
                Embarque.Embarcando = false;

            MudarEstado("Embarque Parado");
        }

        /// <summary>
        /// Escuta por clientes
        /// </summary>
        private void Escutar()
        {
            Listener = new TcpListener(endPoint);
            Listener.Start();
            TcpClient cliente;

            MudarEstado("Escutando na porta " + Porta);
            while (escutar)
            {
                if (!comunicar)
                {
                    if (!Listener.Pending())
                    {
                        debugmsg("escutando...");
                        Thread.Sleep(500);
                        continue;
                    }

                    try
                    {
                        // bloqueia até que um cliente tenha conectado
                        cliente = Listener.AcceptTcpClient();
                        OnConnect(new BalancaEventArgs("Cliente Conectou"));
                        MudarEstado("Requisição vinda de " + cliente.Client.RemoteEndPoint.ToString());
                        comunicar = true;

                        try
                        {
                            debugmsg("comunicando...");
                            Comunicar(cliente);
                        }
                        catch (Exception ex)
                        {                           
                            debugmsg("ERRO AO INICIAR COMUNICADOR: " + ex.Message);
                            comunicar = false;
                        }
                    }
                    catch (ThreadAbortException e)
                    {
                        debugmsg("ABORTADO! " + e.Message);
                        Listener.Stop();
                    }
                }
            }
            debugmsg("fim da escuta");
            Listener.Stop();
        }

        private void Comunicar(object c)
        {
            ASCIIEncoding encoder = new ASCIIEncoding();
            TcpClient client = (TcpClient)c;
            NetworkStream stream = client.GetStream();

            var respostavalida = encoder.GetBytes('\x02' + "82SP|" + '\x03');
            int bytesSent;
            int bytesTotal = 0;
            byte[] resposta = null;

            MudarEstado("Transmitindo para " + client.Client.RemoteEndPoint.ToString());
            while (comunicar)
            {
                bytesSent = 0;
                resposta = new byte[respostavalida.Length];
                client.Client.Receive(resposta);
                //stream.Read(resposta, 0, respostavalida.Length);
                //debugmsg(client.Client.Receive(resposta).ToString() + " - " + encoder.GetString(resposta));

                //debugmsg(encoder.GetString(resposta) + " - " + resposta.Length.ToString() + " - " + encoder.GetString(respostavalida) + " - " + respostavalida.Length.ToString());
                // debugmsg(resposta.SequenceEqual(respostavalida) ? "Resposta Válida!" : "Resposta inválida!");

                if (resposta.SequenceEqual(respostavalida))
                {
                    if (Embarque == null)
                    {
                        //debugmsg("Sem embarque");                    
                        //Thread.Sleep(500);
                        //continue;
                    }
                    else
                    {
                        if (Embarque.Restante == 0)
                            mensagem = String.Empty;
                        else
                        {
                            if (Embarque.Embarcando)
                            {

                                mensagem = MontaString();
                            }
                            else
                                mensagem = String.Empty;
                        }
                    }
                }

                buffer = encoder.GetBytes(mensagem);

                try
                {
                    stream.Write(buffer, 0, buffer.Length);
                    bytesSent += buffer.Length;
                    bytesTotal += bytesSent;

                    byte[] buff = new byte[1];
                    client.Client.Send(buff);

                    OnSend(new BalancaEventArgs("Mensagem Enviada com sucesso"));
                }
                catch (IOException ex)
                {
                    // um erro de socket ocorreu
                    debugmsg("ERRO DE SOCKET: " + ex.ToString());
                    comunicar = false;
                    break;
                }
                catch (Exception ex)
                {
                    debugmsg(ex.Message);
                }

                Thread.Sleep(1000);
            }
            stream.Close();
            client.Close();
            MudarEstado("Fim da transmissão");
        }

        private string MontaString()
        {
            if (Embarque.Embarcando)
                subtotalizadorAtual = Embarque.PoraoAtual.ToString("D2");

            DateTime data = DateTime.Now;
            StringBuilder sb = new StringBuilder();
            sb.Append('\x02');                          // STX
            sb.Append("82SP");                          // Opcode da mensagem
            sb.Append(data.ToString("dd.MM.yy"));       // Data
            sb.Append("|");
            sb.Append(data.ToString("HH:mm"));          // Hora
            sb.Append("|");
            sb.Append("02");                            // Número do terminal (balança)
            sb.Append("|");
            sb.Append(String.Empty);                    // Campo em branco
            sb.Append("|");
            sb.Append(subtotalizadorAtual);             // Subtotalizador atual
            sb.Append("|");
            sb.Append(Embarque.Sequencia);              // Número sequencial
            sb.Append("|");
            sb.Append(pesoAtual);                       // Peso bruto
            sb.Append("|");
            sb.Append(taraVar);                         // Tara
            sb.Append("|");
            sb.Append(velocidadeAtual);                 // Fluxo t/h
            sb.Append("|");
            sb.Append(poroes[Embarque.PoraoAtual]);      // Subtotal
            sb.Append("|");
            sb.Append(Embarque.Embarcado);              // Total Geral
            sb.Append("|");
            sb.Append('\x03');                          // ETX

            return sb.ToString();
        }

        private void Embarcar()
        {
            int min = 50;
            int max = 80;
            MudarEstado("Embarcando");
            while (Embarque.Embarcando)
            {
                var mult = random.Next(-1, 1);

                if (random.Next(0, 100) == 50)
                    velocidadeAtual = 0;

                if (velocidadeAtual < Embarque.Velocidade)
                    velocidadeAtual += random.Next(min, max);
                else if (velocidadeAtual > (Embarque.Velocidade * 1.1))
                    velocidadeAtual -= random.Next(min, max);
                else
                    velocidadeAtual = random.Next((velocidadeAtual - 100), (velocidadeAtual + 100));

                if (velocidadeAtual < 0)
                    velocidadeAtual = 0;

                pesoAtual = velocidadeAtual * random.Next(4, 7);
                
                taraVar = ((Tara + (5 * mult)) * -1);

                lock (thislock)
                {
                    Embarque.Embarcar(pesoAtual);
                    poroes[Embarque.PoraoAtual] += pesoAtual;
                }
                debugmsg("Embarcando " + pesoAtual);

                Thread.Sleep(1000);
            }
            MudarEstado("Embarque Parado.");
        }

        #region [ Events ]
        public event ConnectEventHandler Connected;

        protected virtual void OnConnect(BalancaEventArgs e)
        {
            if (Connected != null)
                Connected(this, e);
        }
        public event DisconnectEventHandler Disconnected;

        protected virtual void OnDisconnect(BalancaEventArgs e)
        {
            if (Disconnected != null)
                Disconnected(this, e);
        }

        public event SendEventHandler Sent;

        protected virtual void OnSend(BalancaEventArgs e)
        {
            if (Sent != null)
                Sent(this, e);
        }

        public event ConnectEventHandler Recieved;

        protected virtual void OnRecieve(BalancaEventArgs e)
        {
            if (Recieved != null)
                Recieved(this, e);
        }

        public event StatusChangeEventHandler StatusChanged;

        protected virtual void OnStatusChange(BalancaEventArgs e)
        {
            if (StatusChanged != null)
                StatusChanged(this, e);
        }
        #endregion

        private void MudarEstado(string text)
        {
            OnStatusChange(new BalancaEventArgs(text));
            //debugmsg(text);
        }

        private void debugmsg(string p)
        {
            System.Diagnostics.Debug.WriteLine("Servidor " + DateTime.Now.ToLongTimeString() + " -> " + p);
        }

        private bool Compare(byte[] b1, byte[] b2)
        {
            if (b1.Length != b2.Length)
                return false;

            for (int i = 0; i < b1.Length; i++)
                if (b1[i] != b2[i])
                    return false;

            return true;
        }
    }

    /// <summary>
    /// Status Event Args
    /// </summary>
    public class BalancaEventArgs : EventArgs
    {
        /// <summary>
        /// The current status
        /// </summary>
        public String Status { get; set; }

        public new static readonly BalancaEventArgs Empty;

        /// <summary>
        /// Simple StatusEventArgs
        /// </summary>
        /// <param name="s">The Status Message</param>
        public BalancaEventArgs(string s)
        {
            Status = s;
        }
    }
}
